package model.simulationmodel;

import java.util.Observable;

import model.simulationmodel.logicelements.LogicSimulationElement;

/**
 * <b>Erbt:</b> <code>Observable</code> <br>
 * <b>Zugehoeriger Observer:</b> <code>LogicCLOCK</code><br>
 * <b>In Verbindung stehende Klassen:</b> <code>SimulationModel</code> <br>
 * <b>Funktion:</b> Dient als zentraler Taktgeber in der Simulation <br>
 * <br>
 * Zentrale Verwaltung der eingesetzten Taktgeber in einer Simulation. Gibt
 * einen Grundtakt gemessen in Millisekunden vor, der durch die
 * <code>notifyObservers()</code>-Methode propagiert wird.
 * 
 * @author Team Locsi
 * 
 */
public class ClockHandler extends Observable {

	private int numberOfPulses;

	/**
	 * Frequenz mit der der ClockHandler den Grundtakt vorgibt gemessen in
	 * Millisekunden.
	 */
	public static final long FREQUENCY = 250;

	/**
	 * Faktor der der Frequenz des Grundtaktes vorrangestellt wird zur zentralen
	 * Regulierung der Simulationsgeschwindigkeit.
	 */
	private double simulationSpeed;
	private SimulationModel sModel;
	private boolean simulationPaused;
	private boolean isInterrupted;
	

	/**
	 * Dieser Konstruktor erzeugt ein neues <code>ClockHandler</code>-Objekt.
	 * 
	 * @param sModel
	 *            <code>SimulationModel</code> - Referenz auf das aktuelle
	 *            <code>SimulationModel</code>
	 */
	public ClockHandler(SimulationModel sModel) {
		this.sModel = sModel;
		this.simulationSpeed = 1;
		this.numberOfPulses = 1;
		this.simulationPaused = false;
	}

	/**
	 * Setzt den Wert, der angibt, ob eine Simulation pausiert ist auf
	 * <code>true</code>
	 */
	public void pauseClocks() {
		this.simulationPaused = true;
	}

	/**
	 * Setzt den Wert, der angibt, ob eine Simulation pausiert ist auf
	 * <code>false</code>
	 */
	public void unpauseClocks() {
		this.simulationPaused = false;
	}

	/**
	 * Gibt den Wert von <code>simulationPaused</code> zurueck
	 * 
	 * @return <code>true</code>, falls Simulation pausiert ist,
	 *         <code>false</code>, sonst
	 */
	public boolean getSimulationPaused() {
		return this.simulationPaused;
	}

	/**
	 * Setzt das Attribut <code>isInterrupted</code>
	 * 
	 * @param isInterrupted
	 *            <code>boolean</code> - der zu setzende Wert
	 */
	public void setIsInterrupted(boolean isInterrupted) {
		this.isInterrupted = isInterrupted;
	}

	/**
	 * Diese Methode gibt zurueck ob die Simulation unterbrochen ist oder nicht
	 * 
	 * @return <code>true</code> falls Simulation unterbrochen,
	 *         <code>false</code> sonst
	 */
	public boolean isInterrupted() {
		return isInterrupted;
	}

	/**
	 * Zuerst wird die <code>checkCalcDuration</code>-Methode aufgerufen. Danach
	 * benachrichtigt das <code>ClockHandler</code>-Objekt alle angemeldeten
	 * Observer, dass diese nun ihren logischen Wert aendern koennen, wobei die
	 * Anzahl der Signale (<code>numberOfPulses</code>) mituebergeben wird.
	 * Daraufhin wird die Anzahl der Signale um 1 erhoeht und die
	 * <code>updateAfterCalc()</code>-Methode des <code>SimulationModel</code>
	 * aufgerufen.
	 * 
	 * @param firstCalcDuration
	 *            <code>long</code> - Zeit in Millisekunden, die bisher zum
	 *            Berechnen benoetigt wurde
	 */
	protected void startClocks(long firstCalcDuration) {
		long startTime;
		checkCalcDuration(firstCalcDuration);
		while (!isInterrupted) {
			startTime = System.currentTimeMillis();
			
			// ist die Simulation pausiert wird gewartet bis im SimulationModel
			// die startSimulation()-Methode aufgerufen wird (also der
			// Play-Button gedrueckt wird
			while (simulationPaused) {
				synchronized (sModel) {
					try {
						sModel.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			this.setChanged();
			this.notifyObservers(numberOfPulses);
			
			//setzt nach jedem takt die changePerPulse wieder auf 0
			for (LogicSimulationElement sElem : sModel.getSimulationElements().values()) {
				sElem.resetChangesPerPulse();
			}
			
			sModel.calcElementsToCalc();
			numberOfPulses++;
			long calcDuration = System.currentTimeMillis() - startTime;
			sModel.updateAfterCalc();
			checkCalcDuration(calcDuration);
		}
	}

	/**
	 * Diese Methode ueberprueft anhand der bisher vergangenen Berechnungszeit,
	 * ob weitergearbeitet werden kann, oder der <code>Thread</code> warten
	 * muss.
	 * 
	 * @param firstCalcDuration
	 *            <code>long</code> - Zeit in Millisekunden, die bisher zum
	 *            Berechnen benoetigt wurde
	 */
	private void checkCalcDuration(long firstCalcDuration) {
		// wenn die Berechnung kuerzer war als der Grundtakt wird um die
		// entsprechende Zeit gewartet
		if (firstCalcDuration < FREQUENCY) {
			try {
				Thread.sleep(Math.max(0, ((long) (FREQUENCY / simulationSpeed))
						- firstCalcDuration));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Diese Methode setzt das <code>ClockHandler</code>-Objekt zum
	 * Ausgangszustand zurueck.
	 */
	protected void resetHandler() {
		numberOfPulses = 1;
		simulationPaused = false;
		isInterrupted = false;
	}

	/**
	 * Setzt die Geschwindigkeit, in der die Simulation ablaeuft. Dient als
	 * Faktor der der Frequenz vorrangestellt wird.
	 * 
	 * @param newSimSpeed
	 *            <code>double</code> - Wert der Geschwindigkeit
	 */
	public void setSimulationSpeed(double newSimSpeed) {
		if (newSimSpeed > 0) {
			simulationSpeed = newSimSpeed;
		}
	}

	/**
	 * Gibt den Wert von <code>SimulationSpeed</code> zurueck
	 * 
	 * @return <code>double</code> - Wert von <code>SimulationSpeed</code>
	 */
	public double getSimulationSpeed() {
		return this.simulationSpeed;
	}

	/**
	 * Gibt zurueck wie viele Pulse gegeben wurden
	 * 
	 * @return <code>int</code> - wert der gegebenen Signale
	 */
	public int getNumberOfPulses() {
		return this.numberOfPulses;
	}
}